﻿#if !SILVERLIGHT

using System;
using System.Web;
using System.Web.Configuration;
using Facebook.Rest;
using Facebook.Schema;
using Facebook.Utility;
using System.Collections.Generic;
using System.IO;

namespace Facebook.Session
{
	internal static class QueryParameters
	{
		public const string AuthToken = "auth_token";
		public const string InCanvas = "fb_sig_in_canvas";
        public const string InIframe = "fb_sig_in_iframe";
        public const string InProfileTab = "fb_sig_in_profile_tab";
		public const string SessionKey = "fb_sig_session_key";
		public const string User = "fb_sig_user";
		public const string ProfileSessionKey = "fb_sig_profile_session_key";
		public const string ProfileUser = "fb_sig_profile_user";
		public const string Expires = "fb_sig_expires";
        public const string ApiKey = "fb_sig_api_key";
	}

    [Serializable]
	internal class CachedSessionInfo
	{
        public CachedSessionInfo(string sessionKey, long userId, DateTime expiryTime, string accessToken)
		{
			SessionKey = sessionKey;
			UserId = userId;
			ExpiryTime = expiryTime;
            AccessToken = accessToken;
		}

		public string SessionKey { get; set; }
		public long UserId { get; set; }
		public DateTime ExpiryTime { get; set; }
        public string AccessToken { get; set; }

        public bool HasExpired
        {
            get
            {
                ///Infinite time (DateHelper.ConvertLocalTimeToUnixTime(ExpiryTime) == 0.0)
                if (DateTime.Now > ExpiryTime && DateHelper.ConvertLocalTimeToUnixTime(ExpiryTime) != 0.0)
                    return true;
                
                return false;
            }

        }
	}

    /// <summary>
    /// Represents json session object sent by facebook on allow request
    /// </summary>
    public class JsonSession
    {
        public string session_key { get; set; }
        public string uid { get; set; }
        public string expires { get; set; }
        public string secret { get; set; }
        public string base_domain { get; set; }
        public string access_token { get; set; }
        public string sig { get; set; }
    }

	/// <summary>
	/// Represents session object for desktop apps
	/// </summary>
	public abstract class CanvasSession : FacebookSession
	{
		#region Public Methods

        /// <summary>
        /// Default Constructor
        /// </summary>
        public CanvasSession()
        { }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="appKey">Application Key</param>
        /// <param name="appSecret">Application Secret</param>
        public CanvasSession(string appKey, string appSecret) : this(appKey, appSecret, true)
        {}

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="appKey">Application Key</param>
        /// <param name="appSecret">Application Secret</param>
        /// <param name="permissions">list of extended permissions to prompt for upon login</param>
        public CanvasSession(string appKey, string appSecret, List<Enums.ExtendedPermissions> permissions) : this(appKey, appSecret, permissions, true)
        {
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="appKey">Application Key</param>
        /// <param name="appSecret">Application Secret</param>
        /// <param name="readRequest">Whether to read the request or not</param>
        public CanvasSession(string appKey, string appSecret, bool readRequest) : this(appKey, appSecret, null, readRequest)
        {}
        /// <summary>
		/// Constructor
		/// </summary>
		/// <param name="appKey">Application Key</param>
		/// <param name="appSecret">Application Secret</param>
        /// <param name="permissions">list of extended permissions to prompt for upon login</param>
        /// <param name="readRequest">Whether to read the request or not</param>
        public CanvasSession(string appKey, string appSecret, List<Enums.ExtendedPermissions> permissions, bool readRequest)
		{
            SetupCanvasSession(appKey, appSecret, permissions, readRequest);
		}

        /// <summary>
        /// SetCanvasSession
        /// </summary>
        /// <param name="appKey">Application Key</param>
        /// <param name="appSecret">Application Secret</param>
        /// <param name="permissions">list of extended permissions to prompt for upon login</param>
        /// <param name="readRequest">Whether to read the request or not</param>
        public void SetupCanvasSession(string appKey, string appSecret, List<Enums.ExtendedPermissions> permissions, bool readRequest)
        {
            RequiredPermissions = permissions;
            if (appKey == null || appSecret == null)
            {
                ApplicationKey = WebConfigurationManager.AppSettings["ApiKey"];
                ApplicationSecret = WebConfigurationManager.AppSettings["Secret"];
            }
            else
            {
                ApplicationKey = appKey;
                ApplicationSecret = appSecret;
            }
            if (readRequest)
                LoadFromRequest();
        }



        private void LoadFromRequest()
        {
            if (string.IsNullOrEmpty(ApplicationKey) || string.IsNullOrEmpty(ApplicationSecret))
            {
                throw new Exception(
                    "Session must have application key and secret before logging in." + Environment.NewLine +
                    "To set them in your web.config, use something like the following:" + Environment.NewLine +
                    "<appSettings>" + Environment.NewLine +
                    "   <add key=\"ApiKey\" value =\"YOURApiKEY\"/>" + Environment.NewLine +
                    "   <add key=\"Secret\" value =\"YOURSECRET\"/>" + Environment.NewLine +
                    "</appSettings>\"");
            }

            if (HttpContext.Current.Response == null || HttpContext.Current.Request == null)
            {
                throw new Exception("Session must have both an HttpRequest object and an HttpResponse object to login.");
            }

            bool inProfileTab = HttpContext.Current.Request[QueryParameters.InProfileTab] == "1";
            string sessionKeyFromRequest = inProfileTab ? HttpContext.Current.Request[QueryParameters.ProfileSessionKey] : HttpContext.Current.Request[QueryParameters.SessionKey];
            string authToken = HttpContext.Current.Request[QueryParameters.AuthToken];


            if (HttpContext.Current.Request[QueryParameters.User] != null)
            {
                UserId = long.Parse(HttpContext.Current.Request[QueryParameters.User]);
            }
            
            string unixExpireTime = HttpContext.Current.Request[QueryParameters.Expires];
            
            // New session json object sent from facebook when using new data
            string mysession = HttpContext.Current.Request["session"];
            if (mysession != null)
            {
                mysession = FixDoubleSessionIssueFromFB(mysession);

                try
                {
                    var jsonSession = Utilities.DeserializeJSONObject<JsonSession>(mysession);
                    sessionKeyFromRequest = jsonSession.session_key;
                    authToken = jsonSession.access_token;
                    unixExpireTime = jsonSession.expires;
                    UserId = long.Parse(jsonSession.uid);
                }
                catch (Exception ex)
                {

                }
            }

            CachedSessionInfo cachedSessionInfo = LoadCachedSession();

            if (!string.IsNullOrEmpty(sessionKeyFromRequest))
            {
                SetSessionProperties(
                    sessionKeyFromRequest,
                    UserId,
                    DateHelper.ConvertUnixTimeToDateTime(long.Parse(unixExpireTime)), authToken);
            }
            else if (HaveValidCachedSession(cachedSessionInfo, authToken, HttpContext.Current.Request[QueryParameters.ApiKey]))
            {
                SetSessionProperties(cachedSessionInfo.SessionKey, cachedSessionInfo.UserId, cachedSessionInfo.ExpiryTime, cachedSessionInfo.AccessToken);
            }
            else if (!string.IsNullOrEmpty(authToken))
            {
                session_info sessionInfo = new Api(this).Auth.GetSession(authToken);
                SetSessionProperties(sessionInfo.session_key, sessionInfo.uid, DateHelper.ConvertUnixTimeToDateTime(sessionInfo.expires), authToken);
            }

        }

        //It seems when you request a canvas session using the old authroization
        //facebook sends the session object twice
        //which kills the Deserialize of the JSON Object
        private string FixDoubleSessionIssueFromFB(string mysession)
        {
            ///remove second session object only if it is there
            if (mysession.IndexOf("},") != -1)
                return mysession.Substring(0, mysession.IndexOf("},") + 1);

            return mysession;

        }
		/// <summary>
		/// Logs in user
		/// </summary>
		public override void Login()
		{
            if (string.IsNullOrEmpty(SessionKey)&& string.IsNullOrEmpty(AccessToken))
            {
                if (!string.IsNullOrEmpty(Code))
                {
                    ObtainAccessTokenFromCode();
                }
                else
                {
                    RedirectToLogin();

                }
            }
            else
            {
                var permissionsString = CheckPermissions();
                if (!string.IsNullOrEmpty(permissionsString))
                {
                    PromptPermissions(GetPermissionsRedirect(GetPermissionUrl(permissionsString, GetNextUrl())));
                }
            }

		}
        /// <summary>
        /// Logs in user
        /// </summary>
        public void ObtainAccessTokenFromCode()
        {
            var webResponse = RestBase.postRequest(string.Format(@"https://graph.facebook.com/oauth/access_token?client_id={0}&client_secret={1}&redirect_uri={2}&code={3)", ApplicationKey, ApplicationSecret, GetNextUrl(), this.Code), null, false);

            using (var streamReader = new StreamReader(webResponse.GetResponseStream()))
            {
                AccessToken = streamReader.ReadToEnd();
            }
            OnLoggedIn(null);
        }



        /// <summary>
        /// Used in GetNextUrl & GetCancelUrl
        /// </summary>
        private string callback_url;
        private string canvas_name;


        /// <summary>
        /// gets callback_url and canvas_name to be used with GetNextUrl & GetCancelUrl
        /// </summary>
        public void GetCanvasNameCallbackUrl()
        {
            if (string.IsNullOrEmpty(callback_url) || string.IsNullOrEmpty(canvas_name))
            {
                var props = new Api(this).Admin.GetAppProperties(new List<string>() { "callback_url", "canvas_name" });
                if (props.ContainsKey("callback_url") && props.ContainsKey("canvas_name") && !string.IsNullOrEmpty(props["callback_url"]) && !string.IsNullOrEmpty(props["callback_url"]))
                {
                    callback_url = props["callback_url"];
                    canvas_name = props["canvas_name"];

                }
            }
        }

        /// <summary>
        /// Get the NextUrl...this is when the user clicks "allow" when authorizing your app
        /// </summary>
        public string GetNextUrl()
        {
            GetCanvasNameCallbackUrl();

            if (!string.IsNullOrEmpty(callback_url) && !string.IsNullOrEmpty(canvas_name))
            {
                // added replace of port 20000 when using library in azure.  Thanks danrussell
                return HttpContext.Current.Request.Url.ToString().ToLower().Replace(":20000", "").Replace(callback_url.ToLower(), string.Format("http://apps.facebook.com/{0}/", canvas_name));
            }
            return null;
                    
        }

        /// <summary>
        /// Get the Cancel Url...this is when the user clicks "don't allow" when authorizing your app
        /// Adds optional parameter CancelUrl to redirect to a page
        /// The is relative your callback_url
        /// </summary>
        public string GetCancelUrl()
        {
            GetCanvasNameCallbackUrl();

            if (!string.IsNullOrEmpty(callback_url) && !string.IsNullOrEmpty(canvas_name))
            {
                string CancelUrl = "";

                //http://apps.facebook.com/appname/CancelUrl
                if (WebConfigurationManager.AppSettings["CancelUrl"] != null)
                    CancelUrl = WebConfigurationManager.AppSettings["CancelUrl"];

                // added replace of port 20000 when using library in azure.  Thanks danrussell
                return HttpContext.Current.Request.Url.ToString().ToLower().Replace(":20000", "").Replace(callback_url.ToLower(), string.Format("http://apps.facebook.com/{0}/{1}", canvas_name, CancelUrl));
            }
            return null;

        }

		/// <summary>
		/// Logs out user
		/// </summary>
		public override void Logout()
		{
		}

		#endregion

		#region Absract Methods

		internal abstract void RedirectToLogin();

        /// <summary>
        /// Prompt for permissions
        /// </summary>
        protected void PromptPermissions(string permissionsRedirect)
        {
            HttpContext.Current.Response.Write(permissionsRedirect);
            HttpContext.Current.Response.End();
        }

        /// <summary>
        /// Get string for redirect response
        /// </summary>
        public abstract string GetRedirect();
        /// <summary>
        /// Get permissions redirect
        /// </summary>
        public abstract string GetPermissionsRedirect(string permissionsUrl);

		internal abstract void CacheSession();

		internal abstract CachedSessionInfo LoadCachedSession();
		
		#endregion

		#region Protected Methods

        /// <summary>
        /// Returns the login url for a canvas page including the api_key query param
        /// </summary>
        protected string GetLoginUrl()
		{
           
            if (!this.UseGraphAuth)
            {
                var url = string.Format("http://www.facebook.com/login.php?api_key={0}&v=1.0&canvas=1", ApplicationKey);
                url += "&return_session=1&session_version=3";
                url += "&cancel_url=" + GetCancelUrl();
                url += "&next=" + GetNextUrl();
                if (RequiredPermissions != null)
                {
                    url += "&req_perms=" + PermissionsToString(RequiredPermissions);
                }
                return url;
            }
            else
            {

                var url = string.Format(@"https://graph.facebook.com/oauth/authorize?client_id={0}&redirect_uri={1}&type=web_server", this.ApplicationKey, GetNextUrl());
                if (RequiredPermissions != null)
                {
                    url += "&scope=" + PermissionsToString(RequiredPermissions);
                }
                return url;
            }
		}

		#endregion

		#region Private Methods

        private void SetSessionProperties(string sessionKey, long userId, DateTime expiryTime, string accessToken)
		{
			SessionKey = sessionKey;
			UserId = userId;
			ExpiryTime = expiryTime;
            AccessToken = accessToken;

			CacheSession();
		}

        private bool HaveValidCachedSession(CachedSessionInfo cachedSessionInfo, string authToken, string apiKeyRequestParam)
        {
            if (cachedSessionInfo == null)
            {
                return false;
            }
            else if (cachedSessionInfo.HasExpired)
            {
                return false;
            }
             // assume for now that all POST requests are valid, since they would have come from a GET just before now
            else if (HttpContext.Current.Request.HttpMethod == "POST")
            {
                return true;
            }
            // this is the case where the user removed the app, but now came back and wants to re-add it.
            // need to check apiKeyRequestParam, too, to make sure this link is coming from Facebook with proper request params,
            // instead of from within our app where request params are not expected
            else if (string.IsNullOrEmpty(authToken) && !string.IsNullOrEmpty(apiKeyRequestParam))
            {
                return false;
            }
            else
            {
                return true;
            }
        }

		#endregion
	}
}

#endif